/*
 * COPYRIGHT:	2010 Christopher Smith, licenced under the GNU GPL v2; see COPYING in the top level directory
 * PROJECT:		sdedit
 * FILE:		win32objtypes.cpp
 * PURPOSE:		support for securable object types specific to Win32
 *				
 */

namespace Win32
{
using namespace ObjectTypes;

namespace Specific
{
	PCWSTR Desktop[DESC_SPECIFIC_SIZE] = 
	{
		L"Read Objects",
		L"Create Window",
		L"Create Menu",
		L"Hook Control",
		L"Journal Record",
		L"Journal Playback",
		L"Enumerate",
		L"Write Objects",
		L"Switch Desktop"
	};

	PCWSTR WindowStation[DESC_SPECIFIC_SIZE] = 
	{
		L"Enum Desktops",
		L"Read Attributes",
		L"Access Clipboard",
		L"Create Desktop",
		L"Write Attributes",
		L"Access Global Atoms",
		L"Exit Windows",
		L"Enumerate",
		L"Read Screen"
	};

	PCWSTR ServiceManager[DESC_SPECIFIC_SIZE] = 
	{
		L"Connect",
		L"Create",
		L"Enumerate",
		L"Lock",
		L"Query Lock",
		L"Modify Boot"
	};

	PCWSTR Service[DESC_SPECIFIC_SIZE] = 
	{
		L"Query Config",
		L"Change Config",
		L"Query Status",
		L"Enum Dependencies",
		L"Start",
		L"Stop",
		L"Pause/Continue",
		L"Interrogate",
		L"Special Control"
	};

	PCWSTR Print[DESC_SPECIFIC_SIZE] = 
	{
		L"Server Admin",
		L"Server Enum",
		L"Printer Admin",
		L"Printer Use",
		L"Job Admin",
		L"Job Read"
	};

	PCWSTR WbemNamespace[DESC_SPECIFIC_SIZE] = 
	{
		L"Enabled",
		L"Method Execute",
		L"Full Write",
		L"Partial Write",
		L"Write Provider",
		L"Remote Access"
	};
}

DelayedImportFunction<DWORD (WINAPI *)(PCWSTR, SE_OBJECT_TYPE, SECURITY_INFORMATION, SID **, SID **, ACL **, ACL **, SECURITY_DESCRIPTOR_RELATIVE **)> GetNamedSecurityInfo_Delayed(L"advapi32", "GetNamedSecurityInfoW");
DelayedImportFunction<DWORD (WINAPI *)(PCWSTR, SE_OBJECT_TYPE, SECURITY_INFORMATION, PSID, PSID, PACL, PACL)> SetNamedSecurityInfo_Delayed(L"advapi32", "SetNamedSecurityInfoW");
DelayedImportFunction<HANDLE (WINAPI *)(ACCESS_MASK, BOOLEAN, DWORD)> OpenThread_Delayed(L"kernel32", "OpenThread");

//object types that cannot detirmine the access granted after opening with MAXIMUM_ALLOWED
class OpaqueAccessNtSecDescObject : public Ntapi::NtSecDescObject
{
public:
	OpaqueAccessNtSecDescObject(USHORT ExtraSdFlags = 0) :
		NtSecDescObject(ExtraSdFlags)
	{}

	//returns a Win32 error code, i.e. 0 for success, ERROR_ACCESS_DENIED for a retryable overstepping of access, or any other value to be passed to RaiseWin32Error
	virtual LONG QuerySecDesc(SECURITY_INFORMATION Si, OUT auto_szpod<SECURITY_DESCRIPTOR_RELATIVE> &Sd) = 0;

	virtual auto_take<SECURITY_DESCRIPTOR_RELATIVE> QuerySecDesc(SECURITY_INFORMATION Si) override
	{
		auto_szpod<SECURITY_DESCRIPTOR_RELATIVE> Sd;

		RaiseOnWin32ErrorT(QuerySecDesc(Si, Sd));

		return Sd.release();
	}

	virtual auto_take<SECURITY_DESCRIPTOR_RELATIVE> QuerySecDesc(SECURITY_INFORMATION *ReadSi, SECURITY_INFORMATION *WriteSi) override
	{
		SECURITY_INFORMATION Si = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION;
		auto_szpod<SECURITY_DESCRIPTOR_RELATIVE> Sd;

		LONG Status = QuerySecDesc(Si, Sd);

		if(Status == ERROR_ACCESS_DENIED)
		{
			Si = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION;

			Status = QuerySecDesc(Si, Sd);
		}

		if(Status == ERROR_ACCESS_DENIED)
		{
			Si = SACL_SECURITY_INFORMATION;

			Status = QuerySecDesc(Si, Sd);
		}

		if(Status)
			RaiseWin32Error(Status);

		*ReadSi = Si;
		*WriteSi = Si;

		return Sd.release();
	}
};

class NamedSecObject : public OpaqueAccessNtSecDescObject
{
public:
	SE_OBJECT_TYPE ObjectType;
	UCHAR Privileges;

	static PCWSTR const *SeObjectTypeToHeaderSet(SE_OBJECT_TYPE ObjectType)
	{
		switch(ObjectType)
		{
		case SE_FILE_OBJECT:
			return Ntapi::Specific::File;
		case SE_SERVICE:
			return Specific::Service;
		case SE_PRINTER:
			return Specific::Print;
		case SE_REGISTRY_WOW64_32KEY:
		case SE_REGISTRY_KEY:
			return Ntapi::Specific::Key;
		case SE_DS_OBJECT:
		case SE_DS_OBJECT_ALL:
			return ObjectTypes::Specific::NullLabels; //Ldap::Specific::NtdsObject;
		case SE_WMIGUID_OBJECT:
			return Specific::WbemNamespace;
		case SE_UNKNOWN_OBJECT_TYPE:
		case SE_PROVIDER_DEFINED_OBJECT:
		case SE_KERNEL_OBJECT:
		case SE_WINDOW_OBJECT:
		case SE_LMSHARE:
		default:
			return NULL;
		}
	}

	NamedSecObject(PCWSTR SrcName, SE_OBJECT_TYPE ObjectType, UCHAR Privileges) :
		ObjectType(ObjectType),
		Object(SeObjectTypeToHeaderSet(ObjectType), SrcName),
		Privileges(Privileges)
	{}

	virtual LONG QuerySecDesc(SECURITY_INFORMATION Si, OUT auto_szpod<SECURITY_DESCRIPTOR_RELATIVE> &Sd) override
	{
		auto_szpod<SECURITY_DESCRIPTOR_RELATIVE, Win32_LocalHeap_pod_allocator> SdLocalAlloc;

		LONG Status = GetNamedSecurityInfo_Delayed(Name, ObjectType, Si, NULL, NULL, NULL, NULL, SdLocalAlloc.assign_indirect());

		if(Status)
			return Status;
		
		Sd = SdLocalAlloc;

		return ERROR_SUCCESS;
	}

	virtual void SetSecDesc(SECURITY_DESCRIPTOR const *Sd, SECURITY_INFORMATION Si) override
	{
		if(Si & DACL_SECURITY_INFORMATION)
		{
			Si |= Sd->Control & SE_DACL_PROTECTED ? PROTECTED_DACL_SECURITY_INFORMATION : 0;
			Si |= Sd->Control & SE_DACL_AUTO_INHERIT_REQ ? UNPROTECTED_DACL_SECURITY_INFORMATION : 0;
		}

		if(Si & SACL_SECURITY_INFORMATION)
		{
			Si |= Sd->Control & SE_SACL_PROTECTED ? PROTECTED_SACL_SECURITY_INFORMATION : 0;
			Si |= Sd->Control & SE_SACL_AUTO_INHERIT_REQ ? UNPROTECTED_SACL_SECURITY_INFORMATION : 0;
		}

		LONG Status = SetNamedSecurityInfo_Delayed(Name, ObjectType, Si, Sd->Owner, Sd->Group, Sd->Dacl, Sd->Sacl);

		if(Status)
			RaiseWin32Error(Status);
	}
};

class CsrObjectBase : public OpaqueAccessNtSecDescObject
{
protected:
	HANDLE Handle;

public:
	CsrObjectBase(HANDLE Handle) :
		Handle(Handle)
	{}

private:
	LONG QuerySecDesc(SECURITY_INFORMATION Si, OUT auto_szpod<SECURITY_DESCRIPTOR_RELATIVE> &Sd)
	{
		ULONG SecurityDescriptorLengthReq = 0x1000;

		while(TRUE)
		{
			Sd.realloc(SecurityDescriptorLengthReq);

			BOOL Result = GetUserObjectSecurity(Handle, &Si, Sd, SecurityDescriptorLengthReq, &SecurityDescriptorLengthReq);

			if(Result)
				break;

			LONG Status = GetLastError();

			if(Status != ERROR_BUFFER_OVERFLOW)
				return Status;
		}

		return ERROR_SUCCESS;
	}

	virtual void SetSecDesc(SECURITY_DESCRIPTOR const *Sd, SECURITY_INFORMATION Si) override
	{
		RaiseOnWin32ErrorT(SetUserObjectSecurity(Handle, &Si, const_cast<SECURITY_DESCRIPTOR *>(Sd)));
	}

protected:
	virtual auto_take<WCHAR> GetObjectName(HANDLE Handle)
	{
		ULONG NameLength = 0x1000; //header + data + a null terminator

		auto_szpod<WCHAR> Name(NULL);

		while(TRUE)
		{
			Name.realloc(NameLength);

			DWORD Status = ERROR_SUCCESS;

			if(!GetUserObjectInformation(Handle, UOI_NAME, Name, NameLength, &NameLength))
				Status = GetLastError();

			if(Status == ERROR_SUCCESS)
				break;

			if(Status != ERROR_BUFFER_OVERFLOW)
				RaiseWin32Error(Status);
		}

		return Name.release();
	}
};

class DesktopCsr : public CsrObjectBase
{
public:
	DesktopCsr(HANDLE Handle) :
		CsrObjectBase(Handle),
		Object(Specific::Desktop, GetObjectName(Handle))
	{}

	virtual ~DesktopCsr()
	{
		CloseDesktop(c_cast<HDESK>(Handle));
	}
};

class DesktopWin32k : public Ntapi::ObjMgrObject
{
public:
	DesktopWin32k(HANDLE Handle, ULONG ExtraSdFlags) :
		ObjMgrObject(Handle, (PWSTR*)0, ExtraSdFlags),
		Object(Specific::Desktop, GetObjectName(Handle))
	{}
};

Object *ReturnNewDesktop(HANDLE Handle, ULONG ExtraSdFlags);

class WindowStationCommon : virtual public Object
{
	virtual HANDLE GetWinstaHandle() = 0;

	static BOOL CALLBACK DestStub(PWSTR Name, LPARAM Context)
	{
		ChildrenWalkDest *Dest = reinterpret_cast<ChildrenWalkDest *>(Context);

		Object *Child = ReturnNewDesktop(OpenDesktop(Name, 0, FALSE, MAXIMUM_ALLOWED), 0);

		Dest->Proc(*Child);

		return TRUE;
	}

	virtual void WalkChildren(ChildrenWalkDest &Dest, UCHAR Privileges) override
	{
		EnumDesktops(c_cast<HWINSTA>(GetWinstaHandle()), &DestStub, reinterpret_cast<LPARAM>(&Dest));
	}
};

class WindowStationCsr : public WindowStationCommon, public CsrObjectBase
{
	virtual HANDLE GetWinstaHandle() override
	{
		return Handle;
	}

public:
	WindowStationCsr(HANDLE Handle) :
		CsrObjectBase(Handle),
		Object(Specific::WindowStation, GetObjectName(Handle))
	{}

	~WindowStationCsr()
	{
		CloseWindowStation(c_cast<HWINSTA>(Handle));
	}
};

class WindowStationWin32k : public WindowStationCommon, public Ntapi::ObjMgrObject
{
	virtual HANDLE GetWinstaHandle() override
	{
		return Handle;
	}

public:
	WindowStationWin32k(HANDLE Handle, ULONG ExtraSdFlags) :
		ObjMgrObject(Handle, (PWSTR*)0, ExtraSdFlags),
		Object(Specific::WindowStation, GetObjectName(Handle))
	{}
};

class ScmObject : public OpaqueAccessNtSecDescObject
{
protected:
	HANDLE Handle;

	ScmObject(HANDLE Handle) :
		Handle(Handle)
	{}

	LONG QuerySecDesc(SECURITY_INFORMATION Si, OUT auto_szpod<SECURITY_DESCRIPTOR_RELATIVE> &Sd)
	{
		ULONG SecurityDescriptorLengthReq = 0x1000;

		while(TRUE)
		{
			Sd.realloc(SecurityDescriptorLengthReq);

			BOOL Result = QueryServiceObjectSecurity(c_cast<SC_HANDLE>(Handle), Si, Sd, SecurityDescriptorLengthReq, &SecurityDescriptorLengthReq);

			if(Result)
				break;

			LONG Status = GetLastError();

			if(Status != ERROR_BUFFER_OVERFLOW)
				return Status;
		}

		return ERROR_SUCCESS;
	}

	virtual void SetSecDesc(SECURITY_DESCRIPTOR const *Sd, SECURITY_INFORMATION Si) override
	{
		RaiseOnWin32ErrorT(SetServiceObjectSecurity(c_cast<SC_HANDLE>(Handle), Si, const_cast<SECURITY_DESCRIPTOR *>(Sd)));
	}

	virtual ~ScmObject()
	{
		CloseServiceHandle(c_cast<SC_HANDLE>(Handle));
	}
};

class ServiceManager : public ScmObject
{
public:
	ServiceManager(HANDLE Handle, PCWSTR Name) :
		ScmObject(Handle),
		Object(Specific::ServiceManager, Name)
	{}
};

class Service : public ScmObject
{
public:
	Service(HANDLE Handle, PCWSTR Name) :
		ScmObject(Handle),
		Object(Specific::Service, Name)
	{}
};

HANDLE OpenWin32File(PCWSTR Name, UCHAR Privileges, BOOL CaseSensitive)
{
	//DWORD ExtraOptions = Privileges & (PRIV_BACKUP | PRIV_RESTORE) ? FILE_FLAG_BACKUP_SEMANTICS : 0;
	DWORD ExtraOptions = FILE_FLAG_BACKUP_SEMANTICS;

	if(CaseSensitive)
		ExtraOptions |= FILE_FLAG_POSIX_SEMANTICS;

	HANDLE Handle = INVALID_HANDLE_VALUE;

	for(ULONG i = 0; i < RTL_NUMBER_OF(Ntapi::AntiLockAttemptOrder); i++)
	{
		Handle = CreateFile(Name, Ntapi::AntiLockAttemptOrder[i] | StoragePrivilegedSecurityRights(Privileges), FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, SECURITY_ANONYMOUS | ExtraOptions, NULL);

		if(Handle == INVALID_HANDLE_VALUE)
		{
			DWORD Err = GetLastError();

			if(Err == ERROR_SHARING_VIOLATION || Err == ERROR_ACCESS_DENIED)
				continue; //try next anti-lock attempt

			{
				OpText Ot(L"Opening a file", Name);

				RaiseWin32Error(Err == ERROR_SUCCESS ? Err : ERROR_FILE_NOT_FOUND); //catch-all

				Ot;
			}
		}
		else
		{
			//if it's a directory, try to get FILE_LIST_DIRECTORY too
			BY_HANDLE_FILE_INFORMATION Info;
			
			if(GetFileInformationByHandle(Handle, &Info))
			{
				if(Info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
				{
					HANDLE Handle2 = CreateFile(Name, FILE_LIST_DIRECTORY | Ntapi::AntiLockAttemptOrder[i] | StoragePrivilegedRights(Privileges), FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, SECURITY_ANONYMOUS | ExtraOptions, NULL);

					if(Handle2 != INVALID_HANDLE_VALUE)
					{
						CloseHandle(Handle);

						return Handle2;
					}
				}
			}

			return Handle;
		}
	}

	RaiseWin32Error(GetLastError());
	return 0;
}

HKEY OpenWin32Key(HKEY Root, PCWSTR Name, UCHAR Privileges)
{
	HKEY Handle;
	LONG Status;
	ACCESS_MASK DesiredAccess = MAXIMUM_ALLOWED | StoragePrivilegedRights(Privileges);
	DWORD Disposition = REG_OPENED_EXISTING_KEY;

	if(!Name)
	{
		ULONG KeyOptions = Privileges & (PRIV_BACKUP | PRIV_RESTORE) ? REG_OPTION_BACKUP_RESTORE : 0;

		RaiseOnWin32Error(RegCreateKeyEx(Root, L"", 0, NULL, KeyOptions, DesiredAccess, NULL, &Handle, &Disposition));
		
		return Handle;
	}

	if(Privileges & (PRIV_BACKUP | PRIV_RESTORE))
	{
		Status = RegOpenKeyEx(Root, Name, 0, DesiredAccess, &Handle);

		if(Status == ERROR_ACCESS_DENIED)
			Status = RegCreateKeyEx(Root, Name, 0, NULL, REG_OPTION_BACKUP_RESTORE, DesiredAccess, NULL, &Handle, &Disposition);

		if(Disposition == REG_CREATED_NEW_KEY) //oops, didn't mean to create a new key
		{
			{
				OpText Ot(L"Deleting this key, accidentally created");
				RaiseOnNtError(NtDeleteKey(reinterpret_cast<HANDLE>(Handle))); //if this doesn't work, we've caused key pollution
				Ot;
			}
			RegCloseKey(Handle);
			Status = ERROR_OBJECT_NOT_FOUND;
		}
	}
	else
		Status = RegOpenKeyEx(Root, Name, 0, DesiredAccess, &Handle);

	if(Status)
		RaiseWin32Error(Status);

	return Handle;
}

Object *ReturnNewDesktop(HANDLE Handle, ULONG ExtraSdFlags)
{
	DWORD Version = GetVersion();
	BYTE Major = (LOBYTE(LOWORD(Version)));
	BYTE Minor = (HIBYTE(LOWORD(Version)));
	(Minor);

	if(Major <= 3)
		return new DesktopCsr(Handle);
	else
		return new DesktopWin32k(Handle, ExtraSdFlags);
};

Object *ReturnNewWindowStation(HANDLE Handle, ULONG ExtraSdFlags)
{
	DWORD Version = GetVersion();
	BYTE Major = (LOBYTE(LOWORD(Version)));
	BYTE Minor = (HIBYTE(LOWORD(Version)));
	(Minor);

	if(Major <= 3)
		return new WindowStationCsr(Handle);
	else
		return new WindowStationWin32k(Handle, ExtraSdFlags);
};

class Win32Key : public OpaqueAccessNtSecDescObject, public ContainerObject //seperate win32 support, for remote keys
{
	HKEY KeyHandle;

	static void PushChildKey(HKEY Handle, PWSTR Name, ChildrenWalkDest &Dest)
	{
		Win32Key Child(Handle, Name);
		Dest.Proc(Child);
	}

public:
	Win32Key(HKEY KeyHandle, PWSTR Name, ULONG ExtraSdFlags OPTIONAL = 0) :
		Object(Ntapi::Specific::Key, Name),
		ContainerObject(Ntapi::Specific::KeyGenericMapping),
		KeyHandle(KeyHandle)
	{}

	~Win32Key()
	{
		RegCloseKey(KeyHandle);
	}

	//static Win32Key *ReturnNew(HKEY KeyHandle, PWSTR Name, ULONG ExtraSdFlags OPTIONAL = 0)
	//{
	//	return new Win32Key(KeyHandle, Name, ExtraSdFlags);
	//}

	virtual BOOLEAN IsContainer() override
	{
		return TRUE;
	}
	virtual BOOLEAN AutoInhSupport() override
	{
		return TRUE;
	}

	virtual void SetSecDesc(const SECURITY_DESCRIPTOR *Sd, SECURITY_INFORMATION Si) override
	{
		LONG Result = RegSetKeySecurity(KeyHandle, Si, c_cast<PSECURITY_DESCRIPTOR>(Sd));

		if(Result != ERROR_SUCCESS)
			RaiseWin32Error(Result);
	}

	virtual LONG QuerySecDesc(SECURITY_INFORMATION Si, OUT auto_szpod<SECURITY_DESCRIPTOR_RELATIVE> &Sd) override
	{
		ULONG SdLenReq = 0;

		LONG Result = RegGetKeySecurity(KeyHandle, Si, NULL, &SdLenReq);

		if(Result != ERROR_INSUFFICIENT_BUFFER)
			return Result;

		Sd.realloc(SdLenReq);

		Result = RegGetKeySecurity(KeyHandle, Si, Sd, &SdLenReq);

		return Result;
	}

	virtual void WalkChildren(ChildrenWalkDest &Dest, UCHAR Privileges) override
	{
		ULONG BufferCount = DIRECTORY_QUERY_BUFFER_SIZE / sizeof(WCHAR);
		auto_szpod<WCHAR> KeyInfo = auto_szpod_empty_ct(BufferCount);
		ULONG Index = 0;

		while(TRUE)
		{
			FILETIME WriteTime;
			LONG Result = RegEnumKeyEx(KeyHandle, Index, KeyInfo, &BufferCount, 0, NULL, NULL, &WriteTime);

			switch(Result)
			{
			case ERROR_SUCCESS:
				break;

			case ERROR_NO_MORE_ITEMS:
				return;

			case ERROR_MORE_DATA:
			case ERROR_BUFFER_OVERFLOW:
			case ERROR_INSUFFICIENT_BUFFER:
				KeyInfo.realloc_ct(BufferCount);
				continue;

			default:
				RaiseWin32Error(Result);
			}

			PushChildKey(OpenWin32Key(KeyHandle, KeyInfo, Privileges), KeyInfo, Dest);

			Index++;
		}
	}
};

DelayedImportFunction<HANDLE (WINAPI *)(DWORD, BOOL, PCWSTR)> OpenJobObjectW_Delayed(L"kernel32", "OpenJobObjectW");

}//namespace Win32